﻿#include "editor/curvedit.h"
#include "ui/controls.h"
#include "draw.h"

using namespace std;
using namespace anip;

PreloadedTexture editor::CurveEditor::keyAddTexture("ui//kpadd.png");
PreloadedTexture editor::CurveEditor::keyDelTexture("ui//kpdel.png");
PreloadedTexture editor::CurveEditor::snapTexture("ui//snap.png");
PreloadedTexture editor::CurveEditor::wrapTexture("ui//wrapmode.png");

const float topMargin = 10;
const float separator = 10;
const float btnSize = 50;
const float bottomMargin = 80;
const float curveResolution = 2.0f;
const float keypointRadius = 8.0f;
const float ctrlpointRadius = 9.0f;
const float ctrlpointDistFactor = 50.0f;
const float dragZoomFactor = 1.01f;
const float scrollZoomFactor = 1.1f;
const float scrollFactor = 15.0f;

const float minGridDistSmall = 3;
const float minGridDistLarge = 25;
const float largeGridSize = 2;
const float smallGridSize = 1;
const float tickFontSize = 12;
const float hoverColorFactor = 1.3f;

const color largeGridColor = { 0.4f, 0.4f, 0.4f, 1.0f };
const color smallGridColor = { 0.2f, 0.2f, 0.2f, 1.0f };
const color curveColor = { 0.4f, 0.6f, 0.8f, 1.0f };
const color continuationColor = { 0.6f, 0.6f, 0.6f, 0.5f };
const color keypointColor = { 0.5f, 0.7f, 0.8f, 1.0f };
const color keypointOutlineColor = { 0.3f, 0.5f, 0.6f, 1.0f };
const color keypointSelectedColor = { 0.2f, 0.3f, 0.8f, 1.0f };
const color controlPointColor = { 0.6f, 0.7f, 0.8f, 1.0f };
const color controlLineColor = { 0.8f, 0.8f, 0.8f, 1.0f };
const color boxSelectionColor = { 0.5f, 0.5f, 0.5f, 0.4f };

struct KeypointDragData
{
	vec2 dragPos;
	float slope, weight;

	KeypointDragData(const kfloat::Keypoint& kp)
		: slope(kp.slope), weight(kp.weight), dragPos(kp.time, kp.value)
	{}
};

struct editor::CurveEditor::PaintData
{
	enum
	{
		None, Pan, Zoom, KeypointDrag, ControlPointDrag, BoxSelect
	} dragMode = None;

	enum
	{
		Select, Add, Delete
	} leftButtonMode = Select;

	bool 
		noCurve = true,
		boxSelectAggregate = false;

	float 
		xmin = 0, xmax = 0, 
		ymin = 0, ymax = 0;
	vec2 
		offset, zoom, dragOffset, dragZoom,
		grid, largeGrid, selRectPos, selRectSize;

	const kfloat::Keypoint
		* hoveredPoint = nullptr,
		* ctrlptHoveredPoint = nullptr,
		* lastActivePoint = nullptr;

	map<const kfloat::Keypoint*, KeypointDragData>
		selection;
};

#pragma region Auxilliary

void editor::CurveEditor::_updateData()
{
	//_data = new PaintData();
	if (_value.size() < 2)
	{
		_data->noCurve = true;
		return;
	}
	
	_data->noCurve = false;
	_data->xmin = _value.first()->time;
	_data->xmax = _value.last()->time;
	_data->ymax = _data->ymin = _value.first()->value;
	for (auto kp = _value.first(); kp != nullptr; kp = kp->next())
	{
		if (kp->value > _data->ymax)
			_data->ymax = kp->value;
		else if (kp->value < _data->ymin)
			_data->ymin = kp->value;
	}
	
}

tuple<float, float> determineGrid(float zoom)
{
smallGrid:
	float step = 1;
	bool dir = step * zoom < minGridDistSmall;
	for (int i = 0; i < 6; i++)
	{
		float n1 = step * (dir ? 10.0f : 0.1f);
		bool dir1 = n1 * zoom < minGridDistSmall;
		if (dir1 != dir)
		{
			float n2 = n1 * 0.5f;
			bool dir2 = n2 * zoom < minGridDistSmall;
			step = (dir2 || dir) ? (dir1 ? step : n1) : n2;
			goto largeGrid;
		}
		step = n1;
	}
	ANIP_FAIL("determineGrid failed");

largeGrid:
	float large = step;
	//dir = large * zoom < minGridDistLarge;
	for (int i = 0; i < 6; i++)
	{
		float n1 = large * 10.0f;
		if (n1 * zoom >= minGridDistLarge)
		{
			float n2 = n1 * 0.5f;
			large = (n2 * zoom < minGridDistLarge) ? n1: n2;
			return { step, large };
		}
		large = n1;
	}
	ANIP_FAIL("determineGrid failed");
}

void editor::CurveEditor::_updateView()
{
	vec2 view = _edit->size() * _data->zoom;
	tie(_data->grid.x, _data->largeGrid.x) = determineGrid(_data->zoom.x);
	tie(_data->grid.y, _data->largeGrid.y) = determineGrid(_data->zoom.y);
}

void editor::CurveEditor::_autoZoom()
{
	vec2 size = _edit->size();
	if (_value.size() == 0)
	{
		_data->zoom = vec2(size.y * 0.3f);
		_data->offset = size * 0.5f / _data->zoom;
		_updateView(); return;
	}

	if (_value.size() == 1)
	{
		_data->zoom = vec2(size.y * 0.3f);
		_data->offset = vec2{ _value.first()->time, _value.first()->value }
			+ size * 0.5f / _data->zoom;
		_updateView(); return;
	}

	vec2 span{ _data->xmax - _data->xmin, _data->ymax - _data->ymin };
	if (span.y == 0) span.y = size.y / size.x * span.x;

	_data->zoom = size / (span * 1.1f);
	_data->offset = vec2{ _data->xmin, _data->ymin } - span * 0.05f;
	_data->hoveredPoint = nullptr;
	_updateView();
}

vec2 editor::CurveEditor::_toEditPos(invec2 pos) const
{
	return _toEditPos(pos, _data->zoom, _data->offset);
}

vec2 editor::CurveEditor::_fromEditPos(invec2 pos) const
{
	return _fromEditPos(pos, _data->zoom, _data->offset);
}

vec2 editor::CurveEditor::_toEditPos(invec2 pos, invec2 zoom, invec2 offset) const
{
	return { (pos.x - offset.x) * zoom.x,
		_edit->size().y - (pos.y - offset.y) * zoom.y };
}

vec2 editor::CurveEditor::_fromEditPos(invec2 pos, invec2 zoom, invec2 offset) const
{
	return { pos.x / zoom.x + offset.x,
		(_edit->size().y - pos.y) / zoom.y + offset.y };
}

void editor::CurveEditor::_deletePoint(
	const kfloat::Keypoint& kp, 
	const kfloat::Keypoint* replace)
{
	// don't forget to update hoveredPoint and lastActivePoint 
	// or you'll end up having dangling pointers ...!!
	if (_data->hoveredPoint == &kp)
		_data->hoveredPoint = replace;
	if (_data->lastActivePoint == &kp)
		_data->lastActivePoint = replace;
	if (_data->ctrlptHoveredPoint == &kp)
		_data->ctrlptHoveredPoint = replace;
	if (_data->selection.count(&kp) > 0)
		_data->selection.erase(&kp);
	_value.deleteKey(kp);
}

void anip::editor::CurveEditor::_deleteSelection()
{
	if (_data->selection.count(_data->hoveredPoint) > 0)
		_data->hoveredPoint = nullptr;
	if (_data->selection.count(_data->ctrlptHoveredPoint) > 0)
		_data->ctrlptHoveredPoint = nullptr;
	if (_data->selection.count(_data->lastActivePoint) > 0)
		_data->lastActivePoint = nullptr;

	for (auto& p : _data->selection)
		_value.deleteKey(*p.first);
	_data->selection.clear();
}

#pragma endregion

#pragma region Event Handlers

void editor::CurveEditor::_scroll(invec2 pos)
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	if (Context::holdingControl())
	{
		vec2 center = _data->offset + _edit->size() * 0.5f / _data->zoom;
		_data->zoom *= pow(scrollZoomFactor, pos.y);
		_data->offset = center - _edit->size() * 0.5f / _data->zoom;
		_updateView();
	}
	else
		_data->offset += pos * vec2{ -1, 1 } / _data->zoom * scrollFactor;
}

bool editor::CurveEditor::_beginDrag(MouseButton btn, invec2 pos)
{
	if (btn == MouseButton::Middle)
	{
		if (Context::holdingControl())
		{
			_data->dragMode = PaintData::Zoom;
			_data->dragOffset = _data->offset;
			_data->dragZoom = _data->zoom;
		}
		else
		{
			_data->dragMode = PaintData::Pan;
			_data->dragOffset = _data->offset;
		}
		return true;
	}
	else if (btn == MouseButton::Left)
	{
		if (_data->selection.empty())
		{
			_data->dragMode = PaintData::BoxSelect;
			_data->selRectPos = _fromEditPos(pos);
			_data->selRectSize = vec2{ 0 };
			_data->boxSelectAggregate = 
				Context::holdingControl() || Context::holdingShift();
			return true;
		}
		else if (_data->ctrlptHoveredPoint != nullptr)
		{
			_data->dragMode = PaintData::ControlPointDrag;
			return true;
		}
		else
		{
			_data->dragMode = PaintData::KeypointDrag;
			for (auto& p : _data->selection)
			{
				p.second.dragPos = { p.first->time, p.first->value };
				p.second.slope = p.first->slope;
				p.second.weight = p.first->weight;
			}
			return true;
		}
	}

	return false;
}

void editor::CurveEditor::_drag(invec2 start, invec2 pos)
{
	postSignal({ ui::Signal::Type::NeedsRedraw });

	vec2 size = _edit->size();
	
	switch (_data->dragMode)
	{
	case PaintData::Pan: {
		_data->offset = _data->dragOffset +
			(start - pos) / _data->zoom * vec2{ 1, -1 };
		break;

	} case PaintData::Zoom: {
		vec2 p1 = _fromEditPos(start, _data->dragZoom, _data->dragOffset);
		vec2 dif = pos - start;
		_data->zoom = _data->dragZoom *
			vec2{ pow(dragZoomFactor, dif.x), pow(dragZoomFactor, -dif.y) };
		_data->offset = p1 - vec2{ start.x, size.y - start.y } / _data->zoom;
		_updateView();
		break;

	} case PaintData::KeypointDrag: {
		map<const kfloat::Keypoint*, KeypointDragData> sel(_data->selection);
		for (auto& p : sel)
		{
			kfloat::Keypoint kp(*p.first);
			vec2 newpos = p.second.dragPos - 
				(start - pos) / _data->zoom * vec2{ 1, -1 };
			if (_snapBtn->isSelected())
				newpos = math::round(newpos / _data->grid) * _data->grid;
			kp.time = newpos.x;
			kp.value = newpos.y;

			const kfloat::Keypoint* newkp = &_value.addKey(kp);
			_deletePoint(*p.first, newkp);
			_data->selection.insert({ newkp, p.second });
		}
		_updateData();
		break;

	} case PaintData::ControlPointDrag: {
		const kfloat::Keypoint* kp = _data->selection.begin()->first;
		vec2 d = _fromEditPos(pos) - vec2{ kp->time, kp->value };
		if (d.x != 0) kp->slope = d.y / d.x;
		break;

	} case PaintData::BoxSelect: {
		vec2 p = _fromEditPos(pos);
		_data->selRectSize = p - _data->selRectPos;
		if (!math::inRectangle(pos, vec2{ 0, 0 }, size))
		{
			if (pos.x < 0) _data->offset.x += pos.x / _data->zoom.x;
			else if (pos.x > size.x) _data->offset.x += (pos.x - size.x) / _data->zoom.x;
			if (pos.y < 0) _data->offset.y -= pos.y / _data->zoom.y;
			else if (pos.y > size.y) _data->offset.y -= (pos.y - size.y) / _data->zoom.y;
			Context::setMousePos(_edit->toAbsolutePos(_toEditPos(p)));
		}
		break;
	} default: 
		break;
	}
}

void editor::CurveEditor::_endDrag()
{
	if (_data->dragMode == PaintData::BoxSelect)
	{
		// cumbersome
		vec2 start = {
			min(_data->selRectPos.x, _data->selRectPos.x + _data->selRectSize.x),
			min(_data->selRectPos.y, _data->selRectPos.y + _data->selRectSize.y) };
		vec2 end = {
			max(_data->selRectPos.x, _data->selRectPos.x + _data->selRectSize.x),
			max(_data->selRectPos.y, _data->selRectPos.y + _data->selRectSize.y) };

		const kfloat::Keypoint* hov = nullptr;
		for (auto kp = _value.first(); kp != nullptr; kp = kp->next())
		{
			if (kp->time < start.x) continue;
			if (kp->time > end.x) break;

			if (kp->value >= start.y && kp->value <= end.y)
			{
				if (_data->selection.count(kp) == 0)
					_data->selection.insert({ kp, KeypointDragData(*kp) });
			}
		}

		if (_data->leftButtonMode == PaintData::Delete)
			_deleteSelection();
		_updateData();
		postSignal({ ui::Signal::Type::NeedsRedraw });
	}
	_data->dragMode = PaintData::None;
}

void editor::CurveEditor::_mouseMove(invec2 pos)
{
	if (_edit->isDragging()) return;

	vec2 size = _edit->size();
	vec2 downLeft = vec2{ 0, size.y };

	_data->hoveredPoint = nullptr;
	_data->ctrlptHoveredPoint = nullptr;
	for (auto kp = _value.first(); kp != nullptr; kp = kp->next())
	{
		if (kp->time < _data->offset.x - keypointRadius) continue;
		if (kp->time > _data->offset.x + size.x + keypointRadius) break;

		vec2 v = _toEditPos({ kp->time, kp->value });
		if (math::distance(v, pos) <= keypointRadius)
			_data->hoveredPoint = kp;
	}
	if (_data->selection.size() == 1)
	{
		const kfloat::Keypoint* kp = _data->selection.begin()->first;
		vec2 v = _toEditPos({ kp->time, kp->value });
		vec2 d = math::normalize(vec2{ 1, -kp->slope } *_data->zoom) * ctrlpointDistFactor;
		if (math::distance(v + d, pos) <= ctrlpointRadius
			|| math::distance(v - d, pos) <= ctrlpointRadius)
			_data->ctrlptHoveredPoint = kp;
	}
	postSignal({ ui::Signal::Type::NeedsRedraw });
}

void editor::CurveEditor::_mouseButton(MouseButton btn, ButtonAction action)
{
	if (btn == MouseButton::Left && action == ButtonAction::Down)
	{
		if (_data->hoveredPoint != nullptr)
		{
			auto kp = _data->hoveredPoint;
			if (_data->leftButtonMode == _data->Delete)
			{
				_deletePoint(*kp);
				_deleteSelection();
				_updateData();
				postSignal({ ui::Signal::Type::NeedsRedraw });
				return;
			}

			if (Context::holdingControl())
			{
				if (_data->selection.count(_data->hoveredPoint) > 0)
					_data->selection.erase(kp);
				else
					_data->selection.insert({ kp, KeypointDragData(*kp) });
			}
			else if (Context::holdingShift())
			{
				if (_data->lastActivePoint == nullptr)
				{
					if (_data->selection.count(kp) == 0)
						_data->selection.insert({ kp, KeypointDragData(*kp) });
				}
				else
				{
					const kfloat::Keypoint *before, *after;
					if (_data->lastActivePoint->time > kp->time)
					{
						before = kp;
						after = _data->lastActivePoint;
					}
					else
					{
						after = kp;
						before = _data->lastActivePoint;
					}
					// in case it hasn't been selected yet, we can't ignore it
					// so we'll stop after it is reached
					after = after->next();
					for (; before != after; before = before->next())
						if (_data->selection.count(before) == 0)
							_data->selection.insert({ before, KeypointDragData(*before) });
				}
			}
			else
			{
				_data->selection.clear();
				_data->selection.insert({ kp, KeypointDragData(*kp) });
			}
			_data->lastActivePoint = kp;
			postSignal({ ui::Signal::Type::NeedsRedraw });
		}
		else if (_data->ctrlptHoveredPoint == nullptr)
		{
			if (_data->leftButtonMode == PaintData::Add)
			{
				vec2 pos = _fromEditPos(_edit->toLocalPos(Context::mousePos()));
				if (_snapBtn->isSelected())
					pos = math::round(pos / _data->grid) * _data->grid;

				const kfloat::Keypoint* kp = nullptr;
				if (pos.x < _data->xmin)
					kp = &_value.addKey({ pos.x, pos.y });
				else
				{
					kp = &_value.keepLastKey(pos.x);
					kp->value = pos.y;
				}
				_data->hoveredPoint = kp;
				_data->lastActivePoint = kp;
				_updateData();
			}
			else
			{
				_data->selection.clear();
				_data->lastActivePoint = nullptr;
			}
			postSignal({ ui::Signal::Type::NeedsRedraw });
		}
	}

	if (btn == MouseButton::Right && action == ButtonAction::Down)
	{
		if (_data->hoveredPoint != nullptr)
		{
			auto kp = _data->hoveredPoint;
			if (!Context::holdingControl() && _data->selection.count(kp) == 0)
			{
				_data->selection.clear();
				_data->selection.insert({ kp, KeypointDragData(*kp) });
				postSignal({ ui::Signal::Type::NeedsRedraw });
			}
		}
	}

	if (btn == MouseButton::Right && action == ButtonAction::Up)
	{
		// disable dragging to prevent conflict
		if (_data->dragMode == PaintData::ControlPointDrag)
			_data->dragMode = PaintData::None;

		ui::Menu& m = ui::MenuHelper::globalMenu(Context::mousePos());
		if (!_data->selection.empty())
		{
			int i = (int)_data->selection.begin()->first->type;
			for (auto& p : _data->selection)
				if (i != (int)p.first->type) { i = -1; break; }

			ui::MenuHelper::checkable(m, 
				L"线性插值", i == (int)Interpolation::Linear,
				[&](ui::MenuTextItem& self)
				{
					self.menu().markDelete();
					for (auto& p : _data->selection)
						p.first->type = Interpolation::Linear;
				});
			ui::MenuHelper::checkable(m, 
				L"三次埃尔米特插值", i == (int)Interpolation::CubicHermite,
				[&](ui::MenuTextItem& self)
				{
					self.menu().markDelete();
					for (auto& p : _data->selection)
						p.first->type = Interpolation::CubicHermite;
				});
			ui::MenuHelper::checkable(m, 
				L"离散", i == (int)Interpolation::Discrete,
				[&](ui::MenuTextItem& self)
				{
					self.menu().markDelete();
					for (auto& p : _data->selection)
						p.first->type = Interpolation::Discrete;
				});
			ui::MenuHelper::separator(m);
			ui::MenuHelper::button(m, L"平滑斜率"s,
				[&](ui::MenuTextItem& self)
				{
					self.menu().markDelete();
					for (auto& p : _data->selection)
						_value.smoothenSlope(*p.first);
				});
			ui::MenuHelper::separator(m);
			ui::MenuHelper::button(m, L"删除"s,
				[&](ui::MenuTextItem& self)
				{
					self.menu().markDelete();
					_deleteSelection();
					_updateData();
				});
			ui::MenuHelper::separator(m);
		}
		ui::MenuHelper::groupHeader(m, L"视图"s);
		ui::MenuHelper::button(m, L"全览视图"s,
			[&](ui::MenuTextItem& self)
			{
				self.menu().markDelete();
				_autoZoom();
			});
		ui::MenuHelper::button(m, L"等比例坐标轴"s,
			[&](ui::MenuTextItem& self)
			{
				self.menu().markDelete();
				_data->zoom = vec2(min(_data->zoom.x, _data->zoom.y));
				_updateView();
			});
		ui::MenuHelper::button(m, L"回到原点"s,
			[&](ui::MenuTextItem& self)
			{
				self.menu().markDelete();
				_data->offset = _edit->size() * -0.5f / _data->zoom;
			});
		ui::MenuHelper::button(m, L"回到曲线上"s,
			[&](ui::MenuTextItem& self)
			{
				self.menu().markDelete();
				float t = _data->offset.x + _edit->size().x * 0.5f / _data->zoom.x;
				t = clamp(t, _data->xmax, _data->xmin);
				_data->offset.y = _value[t] - _edit->size().y * 0.5f / _data->zoom.y;
			});

	}
}

#pragma endregion

#pragma region Drawing

void editor::CurveEditor::_drawHGrid(float grid, bool typ, bool text)
{
	vec2 downLeft = _edit->toAbsolutePos() + vec2{ 0, _edit->size().y };
	vec2 size = _edit->size();
	float offset = _data->offset.y;
	float zoom = _data->zoom.y;

	int i = (int)ceil(offset / grid);
	float val = i * grid;
	float unit = grid * zoom;
	float y = i * unit - offset * zoom;
	while (y <= size.y + unit)
	{
		if (text) Draw::text(
			downLeft + vec2{ 0, -y }, toString(val), Colors::white, _smallFont);
		else Draw::line(
			downLeft + vec2{ 0, -y },
			downLeft + vec2{ size.x, -y },
			typ ? largeGridSize : smallGridSize,
			typ ? largeGridColor : smallGridColor);
		y = (++i) * unit - offset * zoom;
		val = i * grid;
	}
}

void editor::CurveEditor::_drawVGrid(float grid, bool typ, bool text)
{
	vec2 topLeft = _edit->toAbsolutePos();
	vec2 size = _edit->size();
	float offset = _data->offset.x;
	float zoom = _data->zoom.x;

	int i = (int)floor(offset / grid);
	float val = i * grid;
	float unit = grid * zoom;
	float x = i * unit - offset * zoom;
	while (x <= size.x + unit)
	{
		if (text) Draw::text(
			topLeft + vec2{ x, 0 }, toString(val), Colors::white, _smallFont);
		else Draw::line(
				topLeft + vec2{ x, 0 },
				topLeft + vec2{ x, size.y },
				typ ? largeGridSize : smallGridSize,
				typ ? largeGridColor : smallGridColor);
		x = (++i) * unit - offset * zoom;
		val = i * grid;
	}
}

void editor::CurveEditor::_paintView()
{
	//if (_data->noCurve) return;

	vec2 size = _edit->size();
	vec2 topLeft = _edit->toAbsolutePos();
	vec2 downLeft = topLeft + vec2{ 0, size.y };

	Context::setScissor(topLeft, size);
	Context::clear(Colors::black);

	// draw grid
	_drawHGrid(_data->grid.y, false, false);
	_drawVGrid(_data->grid.x, false, false);
	_drawHGrid(_data->largeGrid.y, true, false);
	_drawVGrid(_data->largeGrid.x, true, false);

	// grid numbers
	_drawHGrid(_data->largeGrid.y, true, true);
	_drawVGrid(_data->largeGrid.x, true, true);

	// draw curve
	if (!_data->noCurve)
	{
		int i = 0;
		float x = _data->offset.x;
		float unit = curveResolution * 1.0f / _data->zoom.x;
		vector<vec2> left, mid, right;

		do
		{
			vec2 v = downLeft + vec2{ i * curveResolution,
				(_data->offset.y - _value[x]) * _data->zoom.y };
			if (x <= _data->xmin)
				left.push_back(v);
			else if (x + unit >= _data->xmax)
				right.push_back(v);
			else
				mid.push_back(v);
			x = _data->offset.x + (++i) * unit;
		} while (i * curveResolution <= size.x);

		if (left.size() > 1) Draw::polyline(left.data(), left.size(),
			4.0f, continuationColor);
		if (right.size() > 1) Draw::polyline(right.data(), right.size(),
			4.0f, continuationColor);
		if (mid.size() > 1) Draw::polyline(mid.data(), mid.size(),
			4.0f, curveColor);
	}

	// draw keypoints
	{
		for (auto kp = _value.first(); kp != nullptr; kp = kp->next())
		{
			if (kp->time < _data->offset.x - keypointRadius) continue;
			if (kp->time > _data->offset.x + size.x + keypointRadius) break;
			vec2 v = topLeft + _toEditPos({ kp->time, kp->value }) - vec2(keypointRadius);

			if (_data->selection.count(kp) > 0)
			{
				float fac = _data->hoveredPoint == kp ? hoverColorFactor : 1;
				if (_data->selection.size() == 1)
				{
					// draw slope handles
					vec2 v2 = v + vec2(keypointRadius);
					vec2 d = math::normalize(vec2{ 1, -kp->slope } * _data->zoom) 
						* ctrlpointDistFactor;
					vec2 ss(ctrlpointRadius);
					color col = controlPointColor.mulWithoutAlpha(
						_data->ctrlptHoveredPoint == kp ? hoverColorFactor : 1);
					Draw::line(v2 - d, v2 + d, 3.0f, controlLineColor);
					Draw::ellipse(v2 - d - ss, ss * 2.0f, col);
					Draw::ellipse(v2 + d - ss, ss * 2.0f, col);
				}
				Draw::ellipse(v, { keypointRadius * 2, keypointRadius * 2 },
					keypointSelectedColor.mulWithoutAlpha(fac));
				Draw::ellipseOutline(v,
					{ keypointRadius * 2, keypointRadius * 2 }, 3.0f,
					keypointOutlineColor.mulWithoutAlpha(fac));
			}
			else if (_data->hoveredPoint == kp)
			{
				Draw::ellipse(v, { keypointRadius * 2, keypointRadius * 2 }, 
					keypointColor.mulWithoutAlpha(hoverColorFactor));
				Draw::ellipseOutline(v, 
					{ keypointRadius * 2, keypointRadius * 2 }, 3.0f, 
					keypointOutlineColor.mulWithoutAlpha(hoverColorFactor));
			}
			else
			{
				Draw::ellipse(v + vec2(3.0f), 
					vec2(keypointRadius * 2 - 6.0f), keypointColor);
			}
		}
	}

	if (_data->dragMode == PaintData::BoxSelect)
	{
		vec2 p = topLeft + _toEditPos(_data->selRectPos),
			s = _data->selRectSize * _data->zoom * vec2{ 1, -1 };
		Draw::rect(p, s, { 0.5f, 0.5f, 0.5f, 0.4f });
		// ANIP_FIXME: why does this have no effect
		//Draw::rectOutline(p, s, 2.0f, { 0.7f, 0.7f, 0.7f });
	}

	wstring txt;
	if (_data->selection.size() > 1)
		txt = L"已选中："s + toString((int)_data->selection.size());
	else if (_data->selection.size() == 1)
	{
		auto kp = _data->selection.begin()->first;
		txt = L"选项位置：" + toString(vec2{ kp->time, kp->value });
	}
	else if (_edit->hovered())
		txt = L"指针位置：" + toString(_fromEditPos(_edit->toLocalPos(Context::mousePos())));
	else return;
	Draw::text(topLeft + _edit->size(), txt, Colors::white, _smallFont, { 1, 1 });
}

#pragma endregion

void editor::CurveEditor::_wrapMenu()
{
	auto& m = ui::MenuHelper::globalMenu(
		_wrapBtn->toAbsolutePos() + _wrapBtn->size() * vec2 { 1, 0 });
	ui::MenuHelper::groupHeader(m, L"平铺方法");
	ui::MenuHelper::checkable(m, L"零值", _value.tiling == TemporalTiling::None,
		[&](ui::MenuTextItem& self)
		{
			self.menu().markDelete();
			_value.tiling = TemporalTiling::None;
		});
	ui::MenuHelper::checkable(m, L"边缘", _value.tiling == TemporalTiling::Clamp,
		[&](ui::MenuTextItem& self)
		{
			self.menu().markDelete();
			_value.tiling = TemporalTiling::Clamp;
		});
	ui::MenuHelper::checkable(m, L"外插值", _value.tiling == TemporalTiling::Extrapolate,
		[&](ui::MenuTextItem& self)
		{
			self.menu().markDelete();
			_value.tiling = TemporalTiling::Extrapolate;
		});
	ui::MenuHelper::checkable(m, L"简单平铺", _value.tiling == TemporalTiling::Repeat,
		[&](ui::MenuTextItem& self)
		{
			self.menu().markDelete();
			_value.tiling = TemporalTiling::Repeat;
		});
	ui::MenuHelper::checkable(m, L"镜像", _value.tiling == TemporalTiling::Mirror,
		[&](ui::MenuTextItem& self)
		{
			self.menu().markDelete();
			_value.tiling = TemporalTiling::Mirror;
		});
}

editor::CurveEditor::CurveEditor(Container* parent, const kfloat& k) :
	EditorWindow(parent, { 500, 400 }, L"曲线编辑器"), _value(k),
	_data(new PaintData())
{
	auto& btn = addCloseButton();
	btn.texture = Settings::closeButtonTexture.get();
	_edit = new ui::Draggable<ui::Panel>(this, { topMargin, topMargin });

	_addBtn = new ui::Button(this, vec2{}, vec2{ btnSize });
	_addBtn->toggleOnClick = true;
	_addBtn->texture = keyAddTexture.get();
	_delBtn = new ui::Button(this, vec2{}, vec2{ btnSize });
	_delBtn->toggleOnClick = true;
	_delBtn->texture = keyDelTexture.get();
	_addBtn->onClick = [&](ui::ButtonBase& self)
	{
		_data->leftButtonMode = self.isSelected() ? PaintData::Add : PaintData::Select;
		_delBtn->select(false);
	};
	_delBtn->onClick = [&](ui::ButtonBase& self)
	{
		_data->leftButtonMode = self.isSelected() ? PaintData::Delete : PaintData::Select;
		_addBtn->select(false);
	};

	_snapBtn = new ui::Button(this, vec2{}, vec2{ btnSize });
	_snapBtn->toggleOnClick = true;
	_snapBtn->texture = snapTexture.get();

	_wrapBtn = new ui::Button(this, vec2{}, vec2{ btnSize });
	_wrapBtn->onClick = [&](auto&) { _wrapMenu(); };
	_wrapBtn->texture = wrapTexture.get();

	_edit->onPaint = [&](auto&) { _paintView(); };
	_edit->onMouseMove = [&](auto&, invec2 pos) { _mouseMove(pos); };
	_edit->onMouseButton = [&](auto&, MouseButton btn, ButtonAction act) { _mouseButton(btn, act); };
	_edit->onScroll = [&](auto&, invec2 pos) { _scroll(pos); };

	_edit->onBeginDrag = [&](auto&, MouseButton btn, invec2 pos) { return _beginDrag(btn, pos); };
	_edit->onDrag = [&](auto&, invec2 start, invec2 pos) { _drag(start, pos); };
	_edit->onEndDrag = [&](auto&) { _endDrag(); };

	_smallFont = this->_font;
	_smallFont.size = 12;

	// ANIP_BUG: for unknown reasons I can't call the direct `flow` here (which is
	// why I made this auxilliary method `_flow`). Debugging shows that a temporary
	// ui::Control is added to this window before going out of scope -- but when,
	// and why can this possibly happen? -- leaving behind a dangling pointer. The
	// only thing that seems to be initialized by a direct `flow` but not by `_flow`
	// is a `ui::Flow` struct, which appears to be nothing of interest.
	_flow();

	_updateData();
	_autoZoom();
}

void editor::CurveEditor::_flow()
{
	_edit->setSize(_size - vec2{ topMargin * 2, _margin.y + topMargin + bottomMargin });
	_addBtn->setPosition({ topMargin, _edit->position().y + _edit->size().y + separator });
	_delBtn->setPosition(_addBtn->position() + vec2{ btnSize, 0 });
	_snapBtn->setPosition(_delBtn->position() + vec2{ separator + btnSize, 0 });
	_wrapBtn->setPosition(_snapBtn->position() + vec2{ separator + btnSize, 0 });
}

void editor::CurveEditor::flow(ui::Flow& f)
{
	_flow();
	EditorWindow::flow(f);
}
